package com.hontech.bread.device.mdb

import com.hontech.bread.R
import com.hontech.bread.device.Conn
import com.hontech.bread.utils.hex16
import com.hontech.bread.utils.hex8
import com.hontech.bread.utils.isBitSet
import com.hontech.bread.utils.log
import com.hontech.bread.utils.postEvent
import com.hontech.bread.utils.stringById
import kotlinx.serialization.Serializable
import serial.codec.FlexArray
import serial.codec.Uint16
import serial.codec.Uint32
import serial.codec.Uint8

data class BillAccept(
    val route: Int,
    val typeBit: Int
) {

    fun routeInfo(): String {
        return when (route) {
            0 -> "${stringById(R.string.t1733460848_31)}"
            1 -> "${stringById(R.string.t1733460848_32)}"
            2 -> "${stringById(R.string.t1733460848_33)}"
            3 -> "${stringById(R.string.t1733460848_34)}"
            4 -> "${stringById(R.string.t1733460848_35)}"
            5 -> "${stringById(R.string.t1733460848_36)} – ${stringById(R.string.t1733460848_37)}"
            6 -> "${stringById(R.string.t1733460848_38)}"
            7 -> "${stringById(R.string.t1733460848_39)}"
            else -> "${stringById(R.string.t1733460848_40)}"
        }
    }

    fun value() = BillSetupInfo.get().valueByType(typeBit)

    // ${stringById(R.string.t1733460848_41)}
    fun isAccept() = route == 1

    // ${stringById(R.string.t1733460848_42)}
    fun isRecv() = route == 0

    override fun toString(): String {
        val info = BillSetupInfo.get()
        return "${stringById(R.string.t1733460848_43)}:${info.valueByType(typeBit)} ${routeInfo()}"
    }
}

data class BillStatus(
    val value: Int
) {

    fun isJustReset() = value == 6

    override fun toString(): String {
        if ((value and 0xE0) == 0x40) {
            return "${stringById(R.string.t1733460848_44)}:${value and 0x1F}"
        }

        if ((value and 0xE0) == 0x20) {
            return when (value and 0x1F) {
                1 -> "${stringById(R.string.t1733460848_45)} - ${stringById(R.string.t1733460848_46)}。${
                    stringById(
                        R.string.t1733460848_47
                    )
                }"

                2 -> "${stringById(R.string.t1733460848_48)} - ${stringById(R.string.t1733460848_49)}"
                3 -> "${stringById(R.string.t1733460848_50)} - ${stringById(R.string.t1733460848_51)}"
                4 -> "${stringById(R.string.t1733460848_52)} - ${stringById(R.string.t1733460848_53)}"
                5 -> stringById(R.string.t1733460848_54)
                6 -> "${stringById(R.string.t1733460848_55)} / ${stringById(R.string.t1733460848_56)}"
                7 -> "${stringById(R.string.t1733460848_57)} - ${stringById(R.string.t1733460848_58)}"
                8 -> "ROM ${stringById(R.string.t1733460848_59)} - ${stringById(R.string.t1733460848_60)}(${
                    stringById(
                        R.string.t1733460848_61
                    )
                })"

                9 -> "${stringById(R.string.t1733460848_62)} – ${stringById(R.string.t1733460848_63)} ${
                    stringById(
                        R.string.t1733460848_64
                    )
                }"

                10 -> "${stringById(R.string.t1733460848_65)} – ${stringById(R.string.t1733460848_66)}"
                11, 12, 13, 14 -> stringById(R.string.t1733460848_67)
                15 -> "${stringById(R.string.t1733460848_68)} – VMC ${stringById(R.string.t1733460848_69)}"
                else -> "${stringById(R.string.t1733460848_70)}:${value.hex8()}"
            }
        }

        return when (value) {
            1 -> "${stringById(R.string.t1733460848_71)} - ${stringById(R.string.t1733460848_72)}"
            2 -> "${stringById(R.string.t1733460848_73)} - ${stringById(R.string.t1733460848_74)}"
            3 -> "${stringById(R.string.t1733460848_75)} - ${stringById(R.string.t1733460848_76)}，${
                stringById(
                    R.string.t1733460848_77
                )
            }"

            4 -> "ROM ${stringById(R.string.t1733460848_78)} - ${stringById(R.string.t1733460848_79)}"
            5 -> "${stringById(R.string.t1733460848_80)} - ${stringById(R.string.t1733460848_81)}"
            6 -> "${stringById(R.string.t1733460848_82)} - ${stringById(R.string.t1733460848_83)}，${
                stringById(
                    R.string.t1733460848_84
                )
            }"

            7 -> "${stringById(R.string.t1733460848_85)}1 - ${stringById(R.string.t1733460848_86)}。${
                stringById(
                    R.string.t1733460848_87
                )
            } BILLRETURNED ${stringById(R.string.t1733460848_88)}"

            8 -> "${stringById(R.string.t1733460848_89)} - ${stringById(R.string.t1733460848_90)}"
            9 -> "${stringById(R.string.t1733460848_91)} - ${stringById(R.string.t1733460848_92)} VMC ${
                stringById(
                    R.string.t1733460848_93
                )
            }"

            10 -> "${stringById(R.string.t1733460848_94)} - ${stringById(R.string.t1733460848_95)} ESCROW ${
                stringById(
                    R.string.t1733460848_96
                )
            }"

            11 -> "${stringById(R.string.t1733460848_97)} - ${stringById(R.string.t1733460848_98)}，${
                stringById(
                    R.string.t1733460848_99
                )
            }"

            12 -> "${stringById(R.string.t1733460848_100)} - ${stringById(R.string.t1733460848_101)}（${
                stringById(
                    R.string.t1733460848_102
                )
            }）${stringById(R.string.t1733460848_103)}"

            else -> "${stringById(R.string.t1733460848_104)}:${value.hex8()}"
        }
    }
}

open class BillModelInfoLevel1(
    val manufacturerCode: String,
    val serialNumber: String,
    val model: String,
    val softVersion: String
) {

    companion object {
        private var instance: BillModelInfoLevel1? = null

        fun get(): BillModelInfoLevel1 {
            return instance ?: throw IllegalStateException(
                "${stringById(R.string.t1733460848_105)} ${
                    stringById(
                        R.string.t1733460848_106
                    )
                } ${stringById(R.string.t1733460848_107)}"
            )
        }
    }

    init {
        instance = this
    }
}

class BillModelInfoLevel2(
    manufacturerCode: String,
    serialNumber: String,
    model: String,
    softVersion: String,
    val option: Int
) : BillModelInfoLevel1(
    manufacturerCode, serialNumber, model, softVersion
) {

    companion object {
        private var instance: BillModelInfoLevel2? = null

        fun get(): BillModelInfoLevel2 {
            return instance ?: throw IllegalStateException(
                "${stringById(R.string.t1733460848_108)} ${
                    stringById(
                        R.string.t1733460848_109
                    )
                } ${stringById(R.string.t1733460848_110)}"
            )
        }
    }

    init {
        instance = this
    }
}

@Serializable
data class StackerInfo(
    val full: Boolean,
    val number: Int
)


object Bill {

    private const val RESET = 0x30
    private const val SETUP = 0x31
    private const val SECURITY = 0x32
    private const val POLL = 0x33
    private const val BILL_TYPE = 0x34
    private const val ESCROW = 0x35
    private const val STACKER = 0x36
    private const val EXT = 0x37

    private var resetFlag = false

    suspend fun onceReset() {
        if (!resetFlag) {
            reset(true)
        } else {
            enable()
        }
    }

    suspend fun reset(enable: Boolean = true) {
        Conn.mdbReq(RESET).assertAck()
        resetInit(enable)
        resetFlag = true
    }

    // 1 ${stringById(R.string.t1733460848_111)}
    // 0 ${stringById(R.string.t1733460848_112)}
    suspend fun escrow(b: Int) {
        Conn.mdbReq(ESCROW, Uint8(b)).assertAck()
    }

    private suspend fun resetInit(enable: Boolean) {
        log("SETUP")
        val setupInfo = setup()
        if (setupInfo.level == 1) {
            log("LEVEL1")
            modelInfoLevel1()
        } else if (setupInfo.level == 2) {
            log("LEVEL2")
            modelInfoLevel2()
        }
        log("SECURITY")
        Conn.mdbReq(SECURITY, Uint16(setupInfo.security)).assertAck()
        log("STACK INFO")
        stackerInfo()
        log("BILL TYPE")
        if (enable) {
            val types = setupInfo.allowTypes()
            log("allow:${types.hex16()}")
            billType(types, types)
        } else {
            billType(0, 0)
        }
    }

    suspend fun enable() {
        val setupInfo = setup()
        val types = setupInfo.allowTypes()
        billType(types, types)
    }

    suspend fun disable() {
        billType(0, 0)
    }

    suspend fun billType(accept: Int, escrow: Int) {
        Conn.mdbReq(BILL_TYPE, Uint16(accept), Uint16(escrow)).assertAck()
    }

    suspend fun stackerInfo(): StackerInfo {
        val z1_2 = Uint16()
        Conn.mdbReq(STACKER).parse(z1_2)
        return StackerInfo(
            z1_2.value.isBitSet(15),
            z1_2.value and 0x7FFF
        )
    }

    private suspend fun modelInfoLevel2(): BillModelInfoLevel2 {
        val z1_3 = FlexArray(3)
        val z4_15 = FlexArray(12)
        val z16_27 = FlexArray(12)
        val z28_29 = FlexArray(2)
        val option = Uint32()
        Conn.mdbReq(EXT, Uint8(2)).parse(z1_3, z4_15, z16_27, z28_29, option)
        return BillModelInfoLevel2(
            z1_3.toString(),
            z4_15.toString(),
            z16_27.toString(),
            z28_29.toBCD(),
            option.value
        )
    }

    private suspend fun modelInfoLevel1(): BillModelInfoLevel1 {
        val z1_3 = FlexArray(3)
        val z4_15 = FlexArray(12)
        val z16_27 = FlexArray(12)
        val z28_29 = FlexArray(2)
        Conn.mdbReq(EXT, Uint8(0)).parse(z1_3, z4_15, z16_27, z28_29)
        return BillModelInfoLevel1(
            z1_3.toString(),
            z4_15.toString(),
            z16_27.toString(),
            z28_29.toBCD()
        )
    }

    suspend fun setup(): BillSetupInfo {
        val z1 = Uint8()
        val z2_3 = Uint16()
        val z4_5 = Uint16()
        val z6 = Uint8()
        val z7_8 = Uint16()
        val z9_10 = Uint16()
        val z11 = Uint8()
        val z12_27 = FlexArray(16)
        Conn.mdbReq(SETUP).parse(z1, z2_3, z4_5, z6, z7_8, z9_10, z11, z12_27)
        return BillSetupInfo(
            z1.value,
            z2_3.value,
            z4_5.value,
            z6.value,
            z7_8.value,
            z9_10.value,
            z11.value,
            z12_27
        )
    }

    fun onPollNotify(frame: MdbFrame) {
        val head = frame.dataGet(0)

        if ((head and 0x80) == 0x80) {
            val accept = BillAccept((head shr 4) and 0x07, head and 0x0F)
            postEvent(accept)
            return
        }
        val n = head and 0xE0
        if ((n == 0) || (n == 0x40) || (n == 0x20)) {
            val s = BillStatus(head)
            postEvent(s)
        }
    }
}